<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
	<meta name="viewport" content="width=device-width">
	<title>ebusd</title>
	<link rel="stylesheet" href="css/jsplumb.css">
	<link rel="stylesheet" href="css/style.css">
</head>
<body data-library="dom">
	<div id="main" style="width: 1137px; height: 721px;">
		<div class="ebus" id="ebus">
		</div>
	</div>
	<script src="js/dom.jsPlumb-1.7.5-min.js"></script>
	<script src="js/prototype.js"></script>
	<script>
jsPlumb.ready(function() {
	var instance = jsPlumb.getInstance({
		DragOptions: { cursor: 'pointer', zIndex: 2000 },
		ConnectionOverlays: [
			[ "Arrow", { location: 1 } ],
			[ "Label", {
				location: 0.5,
				id: "label",
				cssClass: "connLabel"
			}]
		],
		Container: "main"
	});
	var selColor = "orange";
	var offColor = "gray";
	var hotColor = "red";
	var warmColor = "orange";
	var coldColor = "blue";
	var offType = {
		connector: "StateMachine",
		paintStyle: { strokeStyle: offColor, lineWidth: 4 },
		hoverPaintStyle: { strokeStyle: selColor },
	};
	var hotType = {
		connector: "StateMachine",
		paintStyle: { strokeStyle: hotColor, lineWidth: 4 },
		hoverPaintStyle: { strokeStyle: selColor },
	};
	var warmType = {
		connector: "StateMachine",
		paintStyle: { strokeStyle: warmColor, lineWidth: 4 },
		hoverPaintStyle: { strokeStyle: selColor },
	};
	var coldType = {
		connector: "StateMachine",
		paintStyle: { strokeStyle: coldColor, lineWidth: 4 },
		hoverPaintStyle: { strokeStyle: selColor },
	};
	instance.registerConnectionType("off", offType);
	instance.registerConnectionType("hot", hotType);
	instance.registerConnectionType("warm", warmType);
	instance.registerConnectionType("cold", coldType);

	// this is the paint style for the connecting lines..
	var connectorPaintStyle = {
		lineWidth: 4,
		strokeStyle: "#808080",
		joinstyle: "round",
		outlineColor: "white",
		outlineWidth: 2
	},
	// .. and this is the hover style.
	connectorHoverStyle = {
		lineWidth: 4,
		strokeStyle: selColor,
		outlineWidth: 2,
		outlineColor: "white"
	},
	endpointHoverStyle = {
		fillStyle: selColor,
		strokeStyle: selColor
	},
	// the definition of source endpoints
	sourceEndpoint = {
		endpoint: "Rectangle",
		paintStyle: {
			strokeStyle: offColor,
			fillStyle: "transparent",
			lineWidth: 4,
			width: 12,
			height: 12
		},
		maxConnections: 1,
		isSource: true,
		connectorStyle: connectorPaintStyle,
		hoverPaintStyle: endpointHoverStyle,
		connectorHoverStyle: connectorHoverStyle,
		dragOptions: {}
	},
	// the definition of target endpoints (will appear when the user drags a connection)
	targetEndpoint = {
		endpoint: "Rectangle",
		paintStyle: {
			strokeStyle: offColor,
			fillStyle: "transparent",
			radius: 6,
			lineWidth: 4,
			width: 12,
			height: 12
		},
		hoverPaintStyle: endpointHoverStyle,
		maxConnections: 1,
		dropOptions: { hoverClass: "hover", activeClass: "active" },
		isTarget: true,
		connector: [ "Flowchart", { stub: [20, 20], gap: 6, cornerRadius: 5, alwaysRespectStubs: true } ]
	},
	init = function(connection) {
		connection.getOverlay("label").setLabel(connection.endpoints[0].getParameter('name'));
	};

	var addEndpoints = function(divId, anchors, needConnections, needUpdates) {
		for (var i = 0; i < anchors.length; i++) {
			var anchor = anchors[i];
			var anchUUID = divId + anchor.name;
			var endpoint = anchor.target ? targetEndpoint : sourceEndpoint;
			if (anchor.distance && endpoint.connector) {
				endpoint = jsPlumbUtil.clone(endpoint);
				if (Array.isArray(anchor.distance)) {
					endpoint.connector[1].stub[0] += anchor.distance[0];
					endpoint.connector[1].stub[1] += anchor.distance[1];
					if (anchor.distance.length>2) {
						endpoint.connector[1].midpoint = anchor.distance[2];
					}
				} else {
					endpoint.connector[1].stub[anchor.target?1:0] += anchor.distance;
				}
			}
			if (anchor.hasOwnProperty('connectWith') && needConnections) {
				needConnections.push(new Array(anchUUID).concat(anchor.connectWith)); // to-circuit, to-endpoint[, item-circuit, item-name[, item-format]]
			}
			var endp = instance.addEndpoint(divId, endpoint, {
				anchor: anchor.anchor, uuid: anchUUID,
				parameters:{name:anchor.name, priority:anchor.priority}
			});
			if (anchor.hasOwnProperty('update') && needUpdates) {
				var update = anchor.update;
				update.endpoint = endp;
				if (anchor.hasOwnProperty('connectWith')) {
					if (needConnections) {
						update.connectWith = anchor.connectWith;
					}
					needUpdates.push(update);
				} else {
					endp.needUpdate = update;
				}
			}
		}
	};

	var each = function(items, fn) {
		for (var i = 0; i < items.length; i++)
			fn(items[i]);
	}
	each(instance.getSelector(".ebus .circuitLabel"), function(item) {
		if (item.getAttribute('topf')) {
			item.style.top = Math.floor(item.parentElement.clientHeight*item.getAttribute('topf'))+'px';
		} else if (item.getAttribute('topmf')) {
			item.style.top = Math.floor(item.parentElement.clientHeight*item.getAttribute('topmf')-item.clientHeight/2)+'px';
		}
		if (item.getAttribute('leftf')) {
			item.style.left = Math.floor(item.parentElement.clientWidth*item.getAttribute('leftf'))+'px';
		} else if (item.getAttribute('leftmf')) {
			item.style.left = Math.floor(item.parentElement.clientWidth*item.getAttribute('leftmf')-item.clientWidth/2)+'px';
		}
	});

	var items = {};
	var sourceFlow, sourceReturn, hwcReturn, ehpFlow, hcFlow, hwcFlow, hcReturn, mixFlow, mixReturn, storHwcFlow, storHwcReturn;
	// suspend drawing and initialise.
	instance.batch(function() {
		// listen for new connections; initialise them the same way we initialise the connections at startup.
		instance.bind("connection", function(connInfo, originalEvent) {
			init(connInfo.connection);
		});

		instance.bind("click", function(conn, originalEvent) {
			if (conn.hasType("hot")) {
				conn.toggleType("hot");
				conn.toggleType("cold");
			} else if (conn.hasType("cold")) {
				conn.toggleType("cold");
			} else {
				conn.toggleType("hot");
			}
		});

		instance.bind("connectionDrag", function(connection) {
			console.log("connection " + connection.id + " is being dragged. suspendedElement is ", connection.suspendedElement, " of type ", connection.suspendedElementType);
		});

		instance.bind("connectionDragStop", function(connection) {
			console.log("connection " + connection.id + " was dragged");
		});

		instance.bind("connectionMoved", function(params) {
			console.log("connection " + params.connection.id + " was moved");
		});
		/*sourceFlow.setType("hot");
		sourceReturn.setType("cold");
		mixFlow.setType("hot");
		mixReturn.setType("cold");
		storHwcFlow.setType("hot");
		storHwcReturn.setType("off");*/

		var heat = false, water = false;
		var switchActions = {
			/*"broadcast/hwcStatus": function(fields){
				console.log("action broadcast/hwcStatus:"+fields);
				if (!fields) return;
				var val = fields[0].value;
				if (val=='on') {
					water = true;
					ehpFlow.addType("hot");
					hcFlow.removeType("hot");
					hwcFlow.addType("hot");
					hcReturn.removeType("cold");
					hwcReturn.addType("cold");
				} else {
					water = false;
					hwcFlow.removeType("hot");
					hwcReturn.removeType("cold");
					if (!heat) {
						ehpFlow.removeType("hot");
						hcFlow.removeType("hot");
						hcReturn.removeType("cold");
					}
				}
			},
			"ehp/HwcHcValve": function(fields) {
				console.log("action ehp/HwcHcValve:"+fields);
				if (!fields) return;
				var val = fields['onoff'].value;
				if (val=='off') { // heat
					heat = true;
					ehpFlow.addType("hot");
					hwcFlow.removeType("hot");
					hcFlow.addType("hot");
					hwcReturn.removeType("cold");
					hcReturn.addType("cold");
				} else {
					water = false;
					hcFlow.removeType("hot");
					hcReturn.removeType("cold");
					if (!water) {
						ehpFlow.removeType("hot");
						hwcFlow.removeType("hot");
						hwcReturn.removeType("cold");
					}
				}
			}*/
		};
		var formatGlobalField = function(name, value) {
			if (name=='signal') {
				return value==1?'OK':'none';
			}
			if (name=='lastup') {
				return new Date(value*1000).toLocaleString().replace(' ', '&nbsp;');
			}
			return '';
		};
		var buildText = function(format, message, circuit, name) {
			var fields = circuit=='global'?message:(message?message.fields:null);
			var ret = '';
			if (format) {
				format = format.split(' ');
				var unit = true;
				for (var i=0; i<format.length; i++) {
					if (ret.length>0) {
						if (ret[ret.length-1]==':') ret += '&nbsp;';
						else ret += ',&nbsp;';
					}
					if (format[i][0]!='$') {
						if (format[i]=='\\n')
							ret += "<br>";
						else if (format[i]=='NOUNIT')
							unit = false
						else
							ret += format[i];
					} else if (fields && fields.hasOwnProperty(format[i].substring(1))){
						var field = fields[format[i].substring(1)];
						ret += field.hasOwnProperty('value')&&field.value!=null?field.value:'?';
						if (field.unit && unit) {
							ret += '&nbsp;'+field.unit;
						}
						unit = true;
					} else if (circuit=='global' && format[i]=='$' && !Array.isArray(fields)) {
						ret += formatGlobalField(name, fields);
					} else {
						ret += '?';
					}
				}
			} else if (circuit=='global' && !Array.isArray(fields)) {
				ret += formatGlobalField(name, fields)
			} else if (fields) {
				for (var i in fields) {
					if (ret.length>0) {
						if (ret[ret.length-1]==':') ret += '&nbsp;';
						else ret += ',&nbsp;';
					}
					var field = fields[i];
					ret += field.hasOwnProperty('value')&&field.value!=null?field.value:'?';
					if (field && field.unit) {
						ret += '&nbsp;'+field.unit;
					}
				}
			} else {
				ret += '?';
			}
			return ret;
		}
		var parentElem = document.getElementById("ebus");
		var getFields = function(circuit, name, numeric, verbose, required) {
			var ret = Array();
			new Ajax.Request('/data/'+circuit+(name?'/'+name:'')+'?exact'+(numeric?'&numeric':'')+(verbose?'&verbose':'')+(required?'&required':''),{
				method:'get',
				asynchronous:false,
				onSuccess: function(trans) {
					ret.push(trans.responseJSON[circuit][name].fields);
				}
			});
			return ret?ret[0]:null;
		};
		var checkCondition = function(condition, allCircuits) {
			for (var k=0; k+2<condition.length; k+=3) { // "and" loop
				var found = false;
				var fields = allCircuits[condition[k]].messages[condition[k+1]].fields;
				var cond = condition[k+2];
				var key = 0;
				if (fields && !fields.hasOwnProperty(key)) {
					for (key in fields) {
						break;
					}
				}
				if (!fields || !fields[key] || !fields[key].hasOwnProperty('value') || !isNaN(Array.isArray(cond) ? cond[0] : cond) && isNaN(fields[key].value)) {
					fields = getFields(condition[k], condition[k+1], key==0, false, true);
					if (!fields || !fields[key] || !fields[key].hasOwnProperty('value')) return false;
				}
				if (Array.isArray(cond)) {
					for (var i=0; i<cond.length; i++) { // "or" loop
						if (fields[key].value==cond[i]) {
							found = true;
							break;
						}
					}
				} else if (fields[key].value==cond) {
					found = true;
				}
				if (!found) return;
			}
			return true;
		};
		var addItemFin = function(parent, name, messages, allCircuits, needEndpoints, needUris, data) {
			if (data.hasOwnProperty('condition') && !checkCondition(data.condition, allCircuits)) return;
			var child = document.createElement("div");
			child.className = "circuit";
			if (data.hasOwnProperty('image')) {
				child.className += " back";
				child.style.backgroundImage = "url(img/"+data.image+")";
				if (data.hasOwnProperty('image-pos')) {
					var pos = data['image-pos'];
					child.style.backgroundPosition = pos[0]+'px '+pos[1]+'px';
				}
			}
			if (data.hasOwnProperty('endpoints')) {
				needEndpoints.push({"name": name, "endpoints": data.endpoints});
			}
			child.id = name;
			child.textContent = data.title;
			var width = data['bounds'][2];
			var height = data['bounds'][3];
			child.style.left = ((parent?parent.offsetLeft:0)+data['bounds'][0])+'px';
			child.style.top  = ((parent?parent.offsetTop:0)+data['bounds'][1])+'px';
			child.style.width = width+'px';
			child.style.height = height+'px';
			parentElem.appendChild(child);
			for (var i in data.items) {
				var item = data.items[i];
				var cmessages = messages;
				var circuit = name;
				if (item.circuit) {
					circuit = item.circuit;
					if (!allCircuits.hasOwnProperty(circuit)) {
						continue;
					}
					cmessages = allCircuits[circuit].messages;
				}
				if (item.type && item.type=='bar') {
					var ichild = document.createElement("div");
					ichild.className = "circuitBar";
					ichild.style.left = Math.floor((item.leftc?item.leftc:item.left)*width)+'px';
					ichild.style.top = Math.floor((item.topc?item.topc:item.top)*height)+'px';
					child.appendChild(ichild);
					if (item.leftc) {
						ichild.style.left = Math.floor(item.leftc*width-ichild.clientWidth/2)+'px';
					}
					if (item.topc) {
						ichild.style.top = Math.floor(item.topc*height-ichild.clientHeight/2)+'px';
					}
					if (item.title) {
						var bchild = document.createElement("div");
						bchild.className = "circuitBarLabel";
						bchild.innerHTML = item.title;
						ichild.appendChild(bchild);
					}
					var values = Array();
					var lastvalues = Array();
					var maxValue = 1;
					for (var which=0; which<2; which++) {
						if (which>0 && !item.lastnames) continue;
						var setvalues = which==0?values:lastvalues;
						var names = which>0 ? item.lastnames : item.names;
						if (!Array.isArray(names)) {
							names = Array(names);
						}
						for (var m=0; m<names.length; m++) {
							var mname = names[m];
							var value = null;
							if (mname && cmessages.hasOwnProperty(mname)) {
								var message = cmessages[mname];
								if (circuit!='global' && !message.passive) needUris.push(circuit+'/'+mname);
								var fields = circuit=='global'?message:(message?message.fields:null);
								if (names.length==1 && fields) {
									for (var f in fields) {
										var field = fields[f];
										value = null;
										if (field && field.hasOwnProperty('value')) {
											value = field.value;
										}
										setvalues[setvalues.length] = value;
										if (value>maxValue) maxValue = value;
									}
									continue;
								} else {
									var field = fields?fields[item.field]:null;
									if (field && field.hasOwnProperty('value')) {
										value = field.value;
									}
								}
							}
							setvalues[setvalues.length] = value;
							if (value>maxValue) maxValue = value;
						}
					}
					for (var which=1; which>=0; which--) {
						if (which>0 && lastvalues.length==0) continue;
						var x = 0;
						var setvalues = which==0?values:lastvalues;
						for (var b=0; b<setvalues.length; b++) {
							var value = setvalues[b];
							var bchild = document.createElement("div");
							bchild.className = "circuitBarLine";
							if (which>0) bchild.style["background-color"] = "grey";
							ichild.appendChild(bchild);
							var h = Math.floor(ichild.clientHeight*value/maxValue);
							bchild.style.height = h+'px';
							bchild.style.top = (ichild.clientHeight-h)+'px';
							bchild.style.left = x+'px';
							x += bchild.clientWidth+2;
						}
					}
					continue;
				}
				var mname = item.name;
				if (!cmessages.hasOwnProperty(mname)) {
					continue;
				}
				var message = cmessages[mname];
				if (circuit!='global' && !message.passive) needUris.push(circuit+'/'+mname);
				var ichild = document.createElement("div");
				ichild.className = "circuitLabel";
				ichild.setAttribute('data-id', circuit+'/'+mname);
				if (item.hasOwnProperty('format')) {
					ichild.setAttribute('data-format', item.format);
				}
				ichild.style.left = Math.floor((item.leftc?item.leftc:item.left)*width)+'px';
				ichild.style.top = Math.floor((item.topc?item.topc:item.top)*height)+'px';
				ichild.innerHTML = buildText(item.format, message, circuit, mname);
				child.appendChild(ichild);
				if (item.leftc) {
					ichild.style.left = Math.floor(item.leftc*width-ichild.clientWidth/2)+'px';
				}
				if (item.topc) {
					ichild.style.top = Math.floor(item.topc*height-ichild.clientHeight/2)+'px';
				}
			}
			if (data.hasOwnProperty('sub')) {
				for (var i in data.sub) {
					var sub = data.sub[i];
					addItemFin(child, sub.name, [], allCircuits, needEndpoints, needUris, sub);
				}
			}
		};
		var addItem = function(parent, name, messages, allCircuits, needEndpoints, needUris) {
			new Ajax.Request('/items/'+name+'.json',{
				method:'get',
				asynchronous:false,
				onSuccess: function(trans) {
					addItemFin(parent, name, messages, allCircuits, needEndpoints, needUris, trans.responseJSON);
				}
			});
		};
		var connectionItems = {};
		var needUpdates = new Array();
		var updateConnections = {};
		var allCircuits = {};
		new Ajax.Request('/data/?verbose',{
			method:'get',
			onSuccess: function(trans) {
				var needEndpoints = new Array();
				var needUris = new Array();
				allCircuits = trans.responseJSON;
				// create items
				for (var cname in allCircuits) {
					if (!allCircuits.hasOwnProperty(cname) || cname.substring(0, 5)=='scan.') {
						continue;
					}
					addItem(null, cname, allCircuits[cname].messages, allCircuits, needEndpoints, needUris);
				}
				// add endpoints
				var needConnections = new Array();
				for (var i=0; i<needEndpoints.length; i++) {
					addEndpoints(needEndpoints[i].name, needEndpoints[i].endpoints, needConnections, needUpdates);
				}
				// establish connections
				var resolveDsts = {};
				var endpoints = instance.selectEndpoints();
				for (var i=0; i<needConnections.length; i++) {
					var data = needConnections[i]; // fromuuid, to-circuit|null, to-endpoint[, item-circuit, item-name[, item-format]]
					var src = data[0], dst;
					if (data[1]==null) {
						var avail = new Array();
						var best = 99;
						endpoints.each(function(endpoint) {
							if (endpoint.getParameter('name')==data[2] && !endpoint.isFull()) {
								var prio = endpoint.getParameter('priority');
								if (avail.length==0 || (isNaN(prio)?prio:99)<best) {
									avail.clear();
									avail.push(endpoint.getUuid());
									best = isNaN(prio)?prio:99;
								}
							}
						});
						dst = avail.length>0 ? avail[0] : null;
						if (dst && !instance.getEndpoint(src).isTarget) {
							resolveDsts["-"+data[2]] = dst;
						}
					} else {
						dst = data[1]+data[2];
					}
					needConnections.splice(i--, 1);
					if (!dst) continue;
					if (instance.getEndpoint(src).isTarget) {
						src = dst;
						dst = data[0];
					}
					var type = 'off';//TODO dst.indexOf("Flow")>=0?'hot':'cold';
					var conn = instance.connect({uuids: [src, dst], editable: true, type: type});
					if (!conn) continue;
					connectionItems[src+"-"+dst] = {conn: conn, data: data};
					var srcendpoint = instance.getEndpoint(src);
					var dstendpoint = instance.getEndpoint(dst);
					if (srcendpoint.needUpdate) {
						var update = srcendpoint.needUpdate;
						delete(srcendpoint.needUpdate);
						update.endpoint = srcendpoint;
						update.connectWith = dstendpoint.getUuid();
						needUpdates.push(update);
						console.log("src need update "+conn);
					}
					if (dstendpoint.needUpdate) {
						var update = dstendpoint.needUpdate;
						delete(dstendpoint.needUpdate);
						update.endpoint = srcendpoint;
						update.connectWith = dstendpoint.getUuid();
						needUpdates.push(update);
						console.log("dst need update "+conn);
					}
					if (data.length>4 && data[3]) { // add label
						var cmessages = allCircuits[data[3]].messages;
						var message = cmessages[data[4]];
						var over = conn.getOverlay("label");
						over.setLabel(buildText(data.length>5?data[5]:null, message, data[3], data[4]));
						var key = data[3]+'/'+data[4];
						if (!needUris[key]) {
							needUris.push(key);
						}
					}
				}
				// request all shown values and enable automatic polling
				for (var i=0; i<needUris.length; i++) {
					new Ajax.Request('/data/'+needUris[i]+'?exact&poll=2',{
						method:'get',
						onSuccess: function(trans) {
							var cname = needUris[i].split('/');
							var mname = cname[1];
							cname = cname[0];
							var message = trans.responseJSON[cname][mname];
							if (message) {
								var elems = $$('div[data-id="'+needUris[i]+'"]');
								each(elems, function(elem) {
									elem.innerHTML = buildText(elem.getAttribute('data-format'), message, cname, mname);
								});
							}
						}
					});
				}
				instance.draggable(jsPlumb.getSelector(".ebus .circuit"), { grid: [20, 20] });
			},
			onFailure: function() {
				console.log("initial request failed");
			},
			onException: function(trans, e) {
				console.log(e);
			}
		});
		var running = false, firstrun = true;
		var since = 0;
		var startRequest = function() {
			if (running) return;
			running = true;
			new Ajax.Request('/data/?verbose&since='+since,{
				method:'get',
				onSuccess: function(trans) {
					if (firstrun) {
						firstrun = false;
					}
					for (var cname in trans.responseJSON) {
						if (!trans.responseJSON.hasOwnProperty(cname)) {
							continue;
						}
						var circuit = trans.responseJSON[cname];
						if (cname!='global' && circuit.hasOwnProperty('messages')) {
							circuit = circuit['messages'];
						}
						for (var mname in circuit) {
							if (!circuit.hasOwnProperty(mname)) {
								continue;
							}
							var message = circuit[mname];
							var lastup = message.lastup;
							if (lastup>since) {
								since = lastup;
							}
							var fields = message.fields;
							if (fields && allCircuits[cname]) {
								var cmessages = allCircuits[cname].messages;
								if (cmessages) {
									var msg = cmessages[mname];
									if (msg) {
										msg.fields = fields;
									}
								}
							}
							var key = cname+'/'+mname;
							var needed = false;
							if (switchActions.hasOwnProperty(key)) {
								switchActions[key](fields);
								needed = true;
							}
							var elems = $$('div[data-id="'+key+'"]');
							if (elems.length>0) {
								needed = true;
							} // else console.log(key+':'+fields);
							if (cname!='global' && !message.fields) {
								continue;
							}
							each(elems, function(elem) {
								elem.innerHTML = buildText(elem.getAttribute('data-format'), message, cname, mname);
							});
						}
					}
					// run needUpdates
					for (var i=0; i<needUpdates.length; i++) {
						var needUpdate = needUpdates[i];
						var src = needUpdate.endpoint.getUuid();
						var data = needUpdate.connectWith;
						if (data) {
							var dst = Array.isArray(data) ? data[0]==null ? resolveDsts[data[1]]: data[0]+data[1] : data; 
							var conn = connectionItems[src+"-"+dst];
							if (conn) {
								var cond = checkCondition(needUpdate.condition, allCircuits);
								var type = needUpdate.type[cond?0:1];
								console.log("need update "+needUpdate.type+": "+conn+" to "+type);
								conn.conn.setType(type);
							} else console.log("need update "+needUpdate.type+": "+conn);
						}
					}
					running = false;
				},
				onFailure: function() {
					console.log("failed");
					running = false;
				},
				onException: function(trans,e) {
					console.log(e);
					running = false;
				}
			});
		};
		startRequest();
		new PeriodicalExecuter(startRequest, 15);
	});
});
	</script>
</body>
</html>
